#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "exml.h"
#include "vmsys.h"

/*
 * Character encoding...
 */

#define ENCODE_UTF8	0		/* UTF-8 */
#define ENCODE_UTF16BE	1		/* UTF-16 Big-Endian */
#define ENCODE_UTF16LE	2		/* UTF-16 Little-Endian */

/* End of file character.
   Some things throughout the library rely on this being -1.  */
#ifndef EOF
# define EOF (-1)
#endif

int		/* O  - Character or EOF */
mxml_string_getc(void *p,		/* I  - Pointer to file */
        int  *encoding);	/* IO - Encoding */

static  __inline int mxml_isspace(int ch)
{
    return (ch == ' ' || ch == '\t' || ch == '\r' ||
            ch == '\n');
}

void *exml_malloc(size_t size)
{
    return (void *)vm_malloc(size);
}

void exml_free(void *ptr)
{
    if (ptr)
      vm_free(ptr);
}

void *exml_realloc(void *ptr, size_t size)
{
    return (void *)vm_realloc(ptr, size);
}

    char *
exml_strdup(const char *s)
{
    char	*t;

    if (s == NULL)
        return (NULL);

    if ((t = (char *)exml_malloc(strlen(s) + 1)) == NULL)
        return (NULL);

    return (strcpy(t, s));
}

void exml_delete_attr(struct exml_attr *attr)
{
	if (!attr)
		return;
    if (attr->name)
        exml_free(attr->name);
   if (attr->value)
       exml_free(attr->value);
}

void exml_delete_node(struct exml_node *node)
{
	struct exml_attr *attr_cur = NULL;
   struct exml_attr *attr_tmp = NULL;
   if (!node)
	   return;
    if (node->name)
        exml_free(node->name);
    if (node->value)
        exml_free(node->value);

	attr_cur = node->attrs;
   while (attr_cur)
   {
      exml_delete_attr(attr_cur);
      attr_tmp = attr_cur->next;
      exml_free(attr_tmp);
      attr_cur = attr_tmp;
   }
}

void exml_delete(struct exml_node *node)
{
	struct exml_node *child_cur = NULL;
    struct exml_node *child_tmp = NULL;
    if (!node)
        return;
	child_cur = node->child;

    exml_delete_node(node);
    exml_free(node);
    while(child_cur)
    {
        child_tmp = child_cur->next;
        exml_delete(child_cur);
        child_cur = child_tmp;
    }
    return;
}

struct exml_node *exml_new_node (struct exml_node *parent, const char *name)
{
    struct exml_node *node = (struct exml_node *)exml_malloc(sizeof(struct exml_node));
    if (!node)
        return NULL;

    memset(node, 0, sizeof(struct exml_node));
    node->name = exml_strdup (name);

    if (parent)
    {
        node->parent = parent;
        node->prev = parent->last_child;
        if (parent->last_child)
            parent->last_child->next = node;
        else
            parent->child = node;
        parent->last_child = node;
    }
    return node;
}

    struct exml_node *
exml_find (struct exml_node *node, const char *name)
{
	struct exml_node *child = NULL;
    if (!node || !name)
        return NULL;

    child = node->child;
    while (child)
    {
        if (strcmp (name, child->name) == 0)
            return child;
        child = child->next;
    }
    return NULL;
}

    struct exml_node *
exml_next (struct exml_node *node)
{
    return node->next;
}

    struct exml_node *
exml_prev (struct exml_node *node)
{
    return node->prev;
}


/*
 * Macro to test for a bad XML character...
 */

#define mxml_bad_char(ch) ((ch) < ' ' && (ch) != '\n' && (ch) != '\r' && (ch) != '\t')

/*
 * 'mxml_get_entity()' - Get the character corresponding to an entity...
 */

    static int				/* O  - Character value or EOF on error */
mxml_get_entity(struct exml_node *parent,	/* I  - Parent node */
        void        *p,		/* I  - Pointer to source */
        int         *encoding)	/* IO - Character encoding */
{
    int	ch;				/* Current character */
    char	entity[64],			/* Entity string */
            *entptr;			/* Pointer into entity */


    entptr = entity;

    while ((ch = mxml_string_getc(p, encoding)) != EOF)
        if (ch > 126 || (!isalnum(ch) && ch != '#'))
            break;
        else if (entptr < (entity + sizeof(entity) - 1))
            *entptr++ = ch;
        else
        {
            break;
        }

    *entptr = '\0';

    if (ch != ';')
    {

        return (EOF);
    }

    if (entity[0] == '#')
    {
        if (entity[1] == 'x')
            ch = strtol(entity + 2, NULL, 16);
        else
            ch = strtol(entity + 1, NULL, 10);
    }

    if (mxml_bad_char(ch))
    {
        return (EOF);
    }

    return (ch);
}


/*
 * 'mxml_add_char()' - Add a character to a buffer, expanding as needed.
 */

    static int				/* O  - 0 on success, -1 on error */
mxml_add_char(int  ch,			/* I  - Character to add */
        char **bufptr,		/* IO - Current position in buffer */
        char **buffer,		/* IO - Current buffer */
        int  *bufsize)		/* IO - Current buffer size */
{
    char	*newbuffer;			/* New buffer value */


    if (*bufptr >= (*buffer + *bufsize - 4))
    {
        /*
         * Increase the size of the buffer...
         */

        if (*bufsize < 1024)
            (*bufsize) *= 2;
        else
            (*bufsize) += 1024;

        if ((newbuffer = exml_realloc(*buffer, *bufsize)) == NULL)
        {
            exml_free(*buffer);
            return (-1);
        }

        *bufptr = newbuffer + (*bufptr - *buffer);
        *buffer = newbuffer;
    }

    if (ch < 0x80)
    {
        /*
         * Single byte ASCII...
         */

        *(*bufptr)++ = ch;
    }
    else if (ch < 0x800)
    {
        /*
         * Two-byte UTF-8...
         */

        *(*bufptr)++ = 0xc0 | (ch >> 6);
        *(*bufptr)++ = 0x80 | (ch & 0x3f);
    }
    else if (ch < 0x10000)
    {
        /*
         * Three-byte UTF-8...
         */

        *(*bufptr)++ = 0xe0 | (ch >> 12);
        *(*bufptr)++ = 0x80 | ((ch >> 6) & 0x3f);
        *(*bufptr)++ = 0x80 | (ch & 0x3f);
    }
    else
    {
        /*
         * Four-byte UTF-8...
         */

        *(*bufptr)++ = 0xf0 | (ch >> 18);
        *(*bufptr)++ = 0x80 | ((ch >> 12) & 0x3f);
        *(*bufptr)++ = 0x80 | ((ch >> 6) & 0x3f);
        *(*bufptr)++ = 0x80 | (ch & 0x3f);
    }

    return (0);
}



/*
 * 'mxml_string_getc()' - Get a character from a string.
 */

    int				/* O  - Character or EOF */
mxml_string_getc(void *p,		/* I  - Pointer to file */
        int  *encoding)	/* IO - Encoding */
{
    int		ch;			/* Character */
    const char	**s;			/* Pointer to string pointer */


    s = (const char **)p;

    if ((ch = (*s)[0] & 255) != 0 || *encoding == ENCODE_UTF16LE)
    {
        /*
         * Got character; convert UTF-8 to integer and return...
         */

        (*s)++;

        switch (*encoding)
        {
            case ENCODE_UTF8 :
                if (!(ch & 0x80))
                {
                    if (mxml_bad_char(ch))
                    {
                        //mxml_error("Bad control character 0x%02x not allowed by XML standard!", ch);
                        return (EOF);
                    }

                    return (ch);
                }
                else if (ch == 0xfe)
                {
                    /*
                     * UTF-16 big-endian BOM?
                     */

                    if (((*s)[0] & 255) != 0xff)
                        return (EOF);

                    *encoding = ENCODE_UTF16BE;
                    (*s)++;

                    return (mxml_string_getc(p, encoding));
                }
                else if (ch == 0xff)
                {
                    /*
                     * UTF-16 little-endian BOM?
                     */

                    if (((*s)[0] & 255) != 0xfe)
                        return (EOF);

                    *encoding = ENCODE_UTF16LE;
                    (*s)++;

                    return (mxml_string_getc(p, encoding));
                }
                else if ((ch & 0xe0) == 0xc0)
                {
                    /*
                     * Two-byte value...
                     */

                    if (((*s)[0] & 0xc0) != 0x80)
                        return (EOF);

                    ch = ((ch & 0x1f) << 6) | ((*s)[0] & 0x3f);

                    (*s)++;

                    if (ch < 0x80)
                    {
                        //mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch);
                        return (EOF);
                    }
                    return (ch);
                }
                else if ((ch & 0xf0) == 0xe0)
                {
                    /*
                     * Three-byte value...
                     */

                    if (((*s)[0] & 0xc0) != 0x80 ||
                            ((*s)[1] & 0xc0) != 0x80)
                        return (EOF);

                    ch = ((((ch & 0x0f) << 6) | ((*s)[0] & 0x3f)) << 6) | ((*s)[1] & 0x3f);

                    (*s) += 2;

                    if (ch < 0x800)
                    {
                        //				  mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch);
                        return (EOF);
                    }

                    /*
                     * Ignore (strip) Byte Order Mark (BOM)...
                     */

                    if (ch == 0xfeff)
                        return (mxml_string_getc(p, encoding));

                    return (ch);
                }
                else if ((ch & 0xf8) == 0xf0)
                {
                    /*
                     * Four-byte value...
                     */

                    if (((*s)[0] & 0xc0) != 0x80 ||
                            ((*s)[1] & 0xc0) != 0x80 ||
                            ((*s)[2] & 0xc0) != 0x80)
                        return (EOF);

                    ch = ((((((ch & 0x07) << 6) | ((*s)[0] & 0x3f)) << 6) |
                                ((*s)[1] & 0x3f)) << 6) | ((*s)[2] & 0x3f);

                    (*s) += 3;

                    if (ch < 0x10000)
                    {
                        //mxml_error("Invalid UTF-8 sequence for character 0x%04x!", ch);
                        return (EOF);
                    }

                    return (ch);
                }
                else
                    return (EOF);

            case ENCODE_UTF16BE :
                /*
                 * Read UTF-16 big-endian char...
                 */

                ch = (ch << 8) | ((*s)[0] & 255);
                (*s) ++;

                if (mxml_bad_char(ch))
                {
                    //mxml_error("Bad control character 0x%02x not allowed by XML standard!", ch);
                    return (EOF);
                }
                else if (ch >= 0xd800 && ch <= 0xdbff)
                {
                    /*
                     * Multi-word UTF-16 char...
                     */

                    int lch;			/* Lower word */


                    if (!(*s)[0])
                        return (EOF);

                    lch = (((*s)[0] & 255) << 8) | ((*s)[1] & 255);
                    (*s) += 2;

                    if (lch < 0xdc00 || lch >= 0xdfff)
                        return (EOF);

                    ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000;
                }

                return (ch);

            case ENCODE_UTF16LE :
                /*
                 * Read UTF-16 little-endian char...
                 */

                ch = ch | (((*s)[0] & 255) << 8);

                if (!ch)
                {
                    (*s) --;
                    return (EOF);
                }

                (*s) ++;

                if (mxml_bad_char(ch))
                {
                    //mxml_error("Bad control character 0x%02x not allowed by XML standard!", ch);
                    return (EOF);
                }
                else if (ch >= 0xd800 && ch <= 0xdbff)
                {
                    /*
                     * Multi-word UTF-16 char...
                     */

                    int lch;			/* Lower word */


                    if (!(*s)[1])
                        return (EOF);

                    lch = (((*s)[1] & 255) << 8) | ((*s)[0] & 255);
                    (*s) += 2;

                    if (lch < 0xdc00 || lch >= 0xdfff)
                        return (EOF);

                    ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000;
                }

                return (ch);
        }
    }
    return (EOF);
}

    struct exml_node  *
exml_load_string (const char *s)
{
    struct exml_node	*node,			/* Current node */
                        *top,
                        *first,			/* First node added */
                        *parent;		/* Current parent node */
    int		ch;			/* Character from file */
    char		*buffer,		/* String buffer */
                *bufptr;		/* Pointer into buffer */
    int		bufsize;		/* Size of buffer */
    int		encoding;		/* Character encoding */

#define TAG_UNKNOW 0
#define TAG_OPEN 1
#define TAG_CLOSE 2
    int flag = TAG_UNKNOW; /* process flag */

    void *p = (void *)&s;
	
    /*
     * Read elements and other nodes from the file...
     */

    if ((buffer = exml_malloc(64)) == NULL)
    {
        // mxml_error("Unable to allocate string buffer!");
        return (NULL);
    }

    bufsize    = 64;
    bufptr     = buffer;
    top = NULL;
    parent     = NULL;
    first      = NULL;
    encoding   = ENCODE_UTF8;

    while ((ch = mxml_string_getc(p, &encoding)) != EOF)
    {

        //
        if (ch == '<')
        {
            // dup value to node
            if (flag == TAG_OPEN)
            {
                *bufptr = '\0';
                if (node && (buffer))
                {
                    if (node->value)
                    {
                        exml_free(node->value);
                    }
                    node->value = exml_strdup (buffer);
                }
            }

            bufptr = buffer;

            while ((ch = mxml_string_getc(p, &encoding)) != EOF)
            {
                if (mxml_isspace(ch) || ch == '>' || (ch == '/' && bufptr > buffer))
                    break;
                else if (ch == '<')
                {
                    goto error;
                }
                else if (ch == '&')
                {
                    if ((ch = mxml_get_entity(parent, p, &encoding)) == EOF)
                        goto error;

                    if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
                        goto error;
                }
                else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
                    goto error;
                else if (((bufptr - buffer) == 1 && buffer[0] == '?') ||
                        ((bufptr - buffer) == 3 && !strncmp(buffer, "!--", 3)) ||
                        ((bufptr - buffer) == 8 && !strncmp(buffer, "![CDATA[", 8)))
                    break;
            }

            *bufptr = '\0';

            if (!strcmp(buffer, "!--"))
            {
                while ((ch = mxml_string_getc(p, &encoding)) != EOF)
                {
                    if (ch == '>' && bufptr > (buffer + 4) &&
                            bufptr[-3] != '-' && bufptr[-2] == '-' && bufptr[-1] == '-')
                        break;
			//		if (ch == '>')
			//			break;
                    else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
                        goto error;
                }

                if (ch != '>')
                    goto error;
                flag = TAG_CLOSE;
            }
            else if (!strcmp(buffer, "![CDATA["))
            {
                while ((ch = mxml_string_getc(p, &encoding)) != EOF)
                {
                    if (ch == '>' && !strncmp(bufptr - 2, "]]", 2))
                        break;
                    else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
                        goto error;
                }

                if (ch != '>')
                    goto error;
                flag = TAG_CLOSE;
            }
            else if (buffer[0] == '?')
            {
                while ((ch = mxml_string_getc(p, &encoding)) != EOF)
                {
                    if (ch == '>' && bufptr > buffer && bufptr[-1] == '?')
				//	if (ch == '>' && bufptr > buffer)
                        break;
                    else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
                        goto error;
                }

                if (ch != '>')
                    goto error;

                *bufptr = '\0';

                if ((node = exml_new_node(parent, buffer)) == NULL)
                {
                    goto error;
                }

                if (node)
                {
                    if (!first)
                        first = node;

                    if (!parent)
                    {
                        parent = node;
                    }
                }

                flag = TAG_CLOSE;
            }
            else if (buffer[0] == '!')
            {
                do
                {
                    if (ch == '>')
                        break;
                }
                while ((ch = mxml_string_getc(p, &encoding)) != EOF);


                if (ch != '>')
                {
                    goto error;
                }
                flag = TAG_CLOSE;
            }
            else if (buffer[0] == '/')
            {
                if (!parent || strcmp(buffer + 1, parent->name))
                {
                    goto error;
                }

                while (ch != '>' && ch != EOF)
                    ch = mxml_string_getc(p, &encoding);

                node   = parent;
                parent = parent->parent;
                flag = TAG_CLOSE;
            }
            else
            {
				int close_tag = 0;
                if ((node = exml_new_node(parent, buffer)) == NULL)
                    goto error;

                
                if (ch == '/')
                {
                    if ((ch = mxml_string_getc(p, &encoding)) != '>')
                    {
                        exml_delete(node);
                        goto error;
                    }
                    ch = '/';
                }
                else
                {
                    do
                    {
                        if (ch == '>')
                        {
                            if (close_tag == 1)
                            {
                                node = parent;
                                parent = parent->parent;
                                flag = TAG_CLOSE;
                            }
                            break;
                        }
                        else if (ch == '/')
                            close_tag = 1;
                    }
                    while ((ch = mxml_string_getc(p, &encoding)) != EOF);
                }

                if (!first)
                    first = node;

                if (ch == EOF)
                    break;

                if (ch != '/')
                {
                    parent = node;
                    flag = TAG_OPEN;
                }
            }

            bufptr = buffer;

        }
        else if (ch == '&')
        {
            if ((ch = mxml_get_entity(parent, p, &encoding)) == EOF)
                goto error;

            if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
                goto error;
        }
        else if (mxml_add_char(ch, &bufptr, &buffer, &bufsize))
            goto error;
    }

    exml_free(buffer);

    if (parent)
    {
        node = parent;

        while (parent->parent != top && parent->parent)
            parent = parent->parent;

        if (node != parent)
        {
            exml_delete(first);

            return (NULL);
        }
    }

    if (parent)
        return (parent);
    else
        return (first);

error:
    exml_delete(first);

    exml_free(buffer);

    return (NULL);
}

